#include "StdAfx.h"
#include "AdpcmCapturer.h"
#include <vector>
#include <string>
#include <stdlib.h>
#include <algorithm>

using namespace std;

//-----------------------------STATIC MEMBERS-----------------------------------

static vector< pair<string, IMoniker *> > s_audioCapturerDevices;

static void ClearCaptureDeviceEntry(pair<string, IMoniker *> entry)
{
	if (entry.second != NULL)
		entry.second->Release();
}

int AdpcmCapturer::GetCaptureDeviceNum()
{
	return s_audioCapturerDevices.size();
}

int AdpcmCapturer::GetCaptureDeviceName(int deviceIndex, char *buf, int len)
{
	if (deviceIndex < 0 || buf == NULL || len <= 0)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	if ((UINT) deviceIndex >= s_audioCapturerDevices.size())
		return ADPCMSDK_ERROR_INDEX_OUT_OF_RANGE;

	string &deviceName = s_audioCapturerDevices[deviceIndex].first;
	if ((UINT) len < deviceName.size() + 1)
		return ADPCMSDK_ERROR_BUFFER_TOO_SMALL;

	string::size_type copyCount = deviceName.copy(buf, len);
	buf[copyCount] = '\0';

	return (int) copyCount;
}

void AdpcmCapturer::ClearCaptureDevice()
{
	for_each(s_audioCapturerDevices.begin(), s_audioCapturerDevices.end(), ClearCaptureDeviceEntry);

	s_audioCapturerDevices.clear();
}

int AdpcmCapturer::EnumCaptureDevice()
{
	const GUID &clsid = CLSID_AudioInputDeviceCategory;
	CComPtr<ICreateDevEnum> pSysDevEnum;
	CComPtr<IEnumMoniker> pEnumCat;

	ClearCaptureDevice();

	// Instantiate the system device enumerator
	HRESULT hr = pSysDevEnum.CoCreateInstance(CLSID_SystemDeviceEnum, NULL, CLSCTX_INPROC);
	if (hr != S_OK)
		return -1;

	hr = pSysDevEnum->CreateClassEnumerator(clsid, &pEnumCat, 0);
	if (hr != S_OK)
		return -1;

	// Enumerate all filters using the category enumerator
	hr = S_OK;
	IMoniker *pMoniker = NULL;
	ULONG cFetched = 0;
	VARIANT varName = {0};
	int nFilters = 0;

	// Enumerate all items associated with the moniker
	while(pEnumCat->Next(1, &pMoniker, &cFetched) == S_OK)
	{
		CComPtr<IPropertyBag> pPropBag;

		// Associate moniker with a file
		hr = pMoniker->BindToStorage(0, 0, IID_IPropertyBag, 
			(void **) &pPropBag);
		if (FAILED(hr))
			continue;

		// Read filter name from property bag
		varName.vt = VT_BSTR;
		hr = pPropBag->Read(L"FriendlyName", &varName, 0);
		if (FAILED(hr))
			continue;

		// Get filter name (converting BSTR name to mb string)
		char filterName[1024];
		size_t convertNumber = 0;
		wcstombs_s(&convertNumber, filterName, varName.bstrVal, 512);
		s_audioCapturerDevices.push_back(make_pair(filterName, pMoniker));
		SysFreeString(varName.bstrVal);
		nFilters++;

		// Intentionally DO NOT release the pMoniker, since it is
		// stored for later use
	}

	return 0;
}


//-----------------------------CONSTRUCTOR & DESTRUCTOR-------------------------

AdpcmCapturer::AdpcmCapturer(int channels, int sampleRate, int bitsPerSample, int bitrate, AudioCodecId codecId, CaptureAudioCallBack callback, int deviceIndex, HAUDIOSESSION newSessionId)
	:m_channels(channels),
	m_sampleRate(sampleRate),
	m_callback(callback),
	m_bitsPerSample(bitsPerSample),
	m_deviceIndex(deviceIndex),
	m_sessionId(newSessionId),
	m_isInitialized(false),
	m_codecId(codecId),
	m_volume(100),
	m_bitrate(bitrate)
{
	if (InitCapturer() < 0)
		return;

	if (!StartCapture())
		DestroyCapturer();
}

AdpcmCapturer::~AdpcmCapturer(void)
{
	DestroyCapturer();
}


//------------------------------PUBLIC METHODS----------------------------------




//------------------------------PRIVATE METHODS---------------------------------

IPin *GetPin(IBaseFilter *pFilter, PIN_DIRECTION PinDir)
{
	BOOL       bFound = FALSE;
	IEnumPins  *pEnum;
	IPin       *pPin;

	HRESULT hr = pFilter->EnumPins(&pEnum);
	if (FAILED(hr))
	{
		return NULL;
	}
	while(pEnum->Next(1, &pPin, 0) == S_OK)
	{
		PIN_DIRECTION PinDirThis;
		pPin->QueryDirection(&PinDirThis);
		if (bFound = (PinDir == PinDirThis))
			break;
		pPin->Release();
	}
	pEnum->Release();
	return (bFound ? pPin : NULL);  
}

int AdpcmCapturer::InitCapturer()
{
#define ERROR_RETURN { m_graphBuilder.Release(); m_filterGraph.Release(); m_captureSource.Release(); m_captureRender.reset(); return -1;}
#define CHECK_RESULT(hr) if (FAILED(hr)) ERROR_RETURN;

	if (m_isInitialized)
		return ADPCMSDK_SUCCESS;

	if (m_deviceIndex < 0 || (UINT) m_deviceIndex >= s_audioCapturerDevices.size())
		return ADPCMSDK_ERROR_INDEX_OUT_OF_RANGE;

	HRESULT hr;

	hr = s_audioCapturerDevices[m_deviceIndex].second->BindToObject(NULL, NULL, IID_IBaseFilter, (void **) &m_captureSource);
	if (FAILED(hr))
		return ADPCMSDK_ERROR_CREATE_FAILED;

	//create a audio playback DirectShow graph

	m_filterGraph.CoCreateInstance(CLSID_FilterGraph, NULL, CLSCTX_INPROC_SERVER);
	if (m_filterGraph == NULL)
		ERROR_RETURN;

	m_graphBuilder.CoCreateInstance(CLSID_CaptureGraphBuilder2, NULL, CLSCTX_INPROC_SERVER);
	if (m_graphBuilder == NULL)
		ERROR_RETURN;

	hr = m_graphBuilder->SetFiltergraph(m_filterGraph);
	CHECK_RESULT(hr);

	hr = m_filterGraph->AddFilter(m_captureSource, L"Audio Capture Source");
	CHECK_RESULT(hr);

	m_captureRender = auto_ptr<CAdpcmCaptureRender>(new CAdpcmCaptureRender(NULL, &hr, m_sessionId, m_callback, m_channels, m_sampleRate, m_bitrate, m_codecId, m_bitsPerSample));
	CHECK_RESULT(hr);

	hr = m_filterGraph->AddFilter(m_captureRender.get(), L"Audio Capture Render");
	CHECK_RESULT(hr);

	hr = m_graphBuilder->RenderStream(&PIN_CATEGORY_CAPTURE, &MEDIATYPE_Audio, m_captureSource, NULL, m_captureRender.get());
	CHECK_RESULT(hr);

	IPin *outPin = GetPin((IBaseFilter *) m_captureSource, PINDIR_OUTPUT);
	if (outPin != NULL)
	{
		CComQIPtr<IAMBufferNegotiation> bufferConfig(outPin);
		if (bufferConfig != NULL)
		{
			ALLOCATOR_PROPERTIES AllocProp;
			AllocProp.cbAlign = -1;  // -1 means no preference.
			AllocProp.cbBuffer = m_bitsPerSample * m_sampleRate * m_channels / 8 / 2;
			AllocProp.cbPrefix = -1;
			AllocProp.cBuffers = -1;
			hr = bufferConfig->SuggestAllocatorProperties(&AllocProp);
		}

		outPin->Release();
	}

	m_isInitialized = true;

	return ADPCMSDK_SUCCESS;
}

int AdpcmCapturer::DestroyCapturer()
{
	StopCapture();

	//release all resources for audio playback(if exists)
	m_graphBuilder.Release();
	m_filterGraph.Release();
	m_captureSource.Release();
	m_captureRender.reset();

	m_isInitialized = false;

	return ADPCMSDK_SUCCESS;
}

bool AdpcmCapturer::StartCapture()
{
	InitCapturer();

	if (!m_isInitialized)
		return true;

	CComQIPtr<IMediaControl> mediaControl(m_filterGraph);
	if (mediaControl == NULL)
		return false;

	mediaControl->Stop();	
	if (FAILED(mediaControl->Run()))
	{
		mediaControl->Stop();	//although graph is not running,but some filters may be running.

		return false;
	}

	return true;
}

void AdpcmCapturer::StopCapture()
{
	if (!m_isInitialized)
		return;

	CComQIPtr<IMediaControl> mediaControl(m_filterGraph);
	if (mediaControl != NULL)
		mediaControl->Stop();
}

int AdpcmCapturer::SetVolume(int volume)
{
	if (!m_isInitialized)
		return ADPCMSDK_ERROR_BAD_SESSION;

	if (volume < MIN_VOLUME || volume > MAX_VOLUME)
		return ADPCMSDK_ERROR_INVALID_PARAM;

	m_volume = volume;

	m_captureRender->SetVolume(volume);

	return ADPCMSDK_SUCCESS;
}

int AdpcmCapturer::GetVolume()
{
	if (!m_isInitialized)
		return ADPCMSDK_ERROR_BAD_SESSION;

	return (int) m_volume;
}
